﻿using System;
using CQRS.Events;
using CQRS.General;
using CQRS.Reports;
using CQRS.Reports.SqlQueryBuilder;
using Castle.MicroKernel.Registration;
using Castle.Windsor;

namespace CQRS.Infrastructure
{
    namespace Hermes.Infrastructure.ServiceLocator
    {
        /// <summary>
        /// Singletone class for Windsor container
        /// </summary>
        public class IoC
        {
            #region Public static members

            /// <summary>
            /// Get Windsor Container
            /// </summary>
            public static IWindsorContainer Container
            {
                get { return _container; }
            }

            /// <summary>Event fired when <see cref="Resolve{T}()"/> is run. If returning an instance that is used
            /// instead of the default resolved one. Return null to use the registered instance for the type.
            /// Use this only for e.g. returning mocks in unit tests.
            /// NOTE: If returning a custom instance you you will be responsible of its lifecycle since this bypasses Windsor framework.
            /// </summary>
            public static event Func<Type, object> OnBeforeIocResolve;

            /// <summary>
            /// Register specific interface and its implementation
            /// </summary>
            public static void Register(string key, Type interfaceType, Type implementationType)
            {
                ComponentRegistration<object> componentRegistration =
                    Component.For(interfaceType).ImplementedBy(implementationType).LifeStyle.Transient;
                //If key isn't supplied, we use default registration without key (i.e. using full type as key)
                if (key != null)
                    componentRegistration.Named(key);
                _container.Register(componentRegistration);
            }

            /// <summary>
            /// Register specific interface and its implementation, using fulle name of <param name="implementationType"/> as the key.
            /// </summary>
            public static void Register(Type interfaceType, Type implementationType)
            {
                Register(null, interfaceType, implementationType);
            }

            public static void RegisterAll()
            {
                RegisterDomainSide();
                RegisterQuerySide();
            }

            private static void RegisterQuerySide()
            {
                Register(typeof(ISqlCreateTableStatementBuilder), typeof(SqlTableCreateStatementBuilder));

                Register(typeof(ISqlDeleteStatementBuilder), typeof(SqlDeleteStatementBuilder));
                Register(typeof(ISqlInsertStatementBuilder), typeof(SqlInsertStatementBuilder));
                Register(typeof(ISqlUpdateStatementBuilder), typeof(SqlUpdateStatementBuilder));
                Register(typeof(ISqlSelectStatementBuilder), typeof(SqlSelectStatementBuilder));


                Register(typeof(IReportingRepository), typeof(MsSqlReportingRepository));
            }

            private static void RegisterDomainSide()
            {
                Register(typeof(IBus<>), typeof(Bus<>));
                RegisterInstanceAsSingleton <IMessageRouter>(new MessageRouter());
                Register(typeof(IEventRepository<>), typeof(EventRepository<>));
                Register(typeof(IDomainRepository<>), typeof(DomainRepository<>));

                Register(typeof(IMongoDatabaseHelper), typeof(MongoDatabaseHelper));
            }


            /// <summary>Registers the specified type with an existing instance as a singleton.</summary>
            public static void RegisterInstanceAsSingleton<TType>(TType instance) where TType : class
            {
                ComponentRegistration<TType> componentRegistration = Component.For<TType>().Instance(instance);
                _container.Register(componentRegistration.LifeStyle.Singleton);
            }

            /// <summary>Registers the specified type with an existing instance as a singleton with the specified key name.</summary>
            public static void RegisterInstanceAsSingleton<TType>(TType instance, string key) where TType : class
            {
                ComponentRegistration<TType> componentRegistration = Component.For<TType>().Instance(instance);
                _container.Register(componentRegistration.LifeStyle.Singleton.Named(key));
            }

            /// <summary>
            /// Resolve interface, note that caller is responsible for releasing the returned instance.
            /// </summary>
            /// <typeparam name="T">Interface type</typeparam>
            /// <seealso cref="OnBeforeIocResolve"/>
            public static T Resolve<T>() where T : class
            {
                object overriddenInstance = null;

                //Copy to local var to be thread safe
                Func<Type, object> evtCb = OnBeforeIocResolve;
                if (evtCb != null)
                    overriddenInstance = evtCb(typeof (T));

                if (overriddenInstance != null)
                {
                    var returnedType = overriddenInstance as T;
                    if (returnedType != null)
                        return returnedType;
                    throw new InvalidOperationException(string.Format("Expected T to be instance of {0} but was {1}",
                                                                      typeof (T).Name,
                                                                      overriddenInstance.GetType().Name));
                }
                return _container.Resolve<T>();
            }


            /// <summary>
            /// Resolve by Interface and key name, note that caller is responsible of releaseing the returned instance.
            /// </summary>
            /// <typeparam name="T">Interface</typeparam>
            /// <param name="name">Key name</param>
            /// <returns></returns>
            public static T Resolve<T>(string name)
            {
                return _container.Resolve<T>(name);
            }

            /// <summary>
            /// Resolve by type
            /// </summary>
            /// <param name="type"></param>
            /// <returns></returns>
            public static object Resolve(Type type)
            {
                return _container.Resolve(type);
            }

            #endregion

            private static readonly IWindsorContainer _container = new WindsorContainer();
        }
    }
}